/*
 * RunPipeline.java
 *
 * Created on September 2, 2006, 1:22 PM
 *
 * (C) 2006 R. Alexander Milowski
 */

package com.smallx.xproc.tools;

import com.smallx.xproc.compiler.FlowCompiler;
import java.io.FileWriter;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.infoset.component.Component;
import org.infoset.component.ComponentContextBase;
import org.infoset.component.ComponentDefinition;
import org.infoset.component.ComponentRegistry;
import org.infoset.component.ComponentRegistryReader;
import org.infoset.xml.Document;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.WriterItemDestination;
import org.infoset.xproc.Pipeline;
import org.infoset.xproc.PipelineCompiler;

/**
 *
 * @author alex
 */
public class XProc extends CommandLine {
   
   static final String REGISTRY_OPT = "r";
   /** Creates a new instance of RunPipeline */
   public XProc() {
   }
   
   protected void usage() {
      System.err.println(this.getClass().getName()+" pipeline { -r registry-loc } { -out port=uri ...} port=input { port2=input2 ...}");
   }
   
   protected void setup() {
      setMinimumArguments(2);
      allowMoreArguments();
      allowOptionWithArguments("out");
      allowOptionWithArgument(REGISTRY_OPT);
   }
   
   protected ItemDestination getOutput(Writer out) 
      throws IOException,XMLException
   {
      return new WriterItemDestination(out,"UTF-8");      
   }
   
   protected Writer openURI(URI location) 
      throws IOException
   {
      if (location.getScheme().equals("file")) {
         return new OutputStreamWriter(new FileOutputStream(location.getSchemeSpecificPart()),"UTF-8");
      } else {
         throw new IOException("Unsupported URI scheme "+location.getScheme()+" for output.");
      }
      
   }
   
   protected int runProgram() {
      String [] args = getArguments();
      try {
         URI registryLoc = hasOption(REGISTRY_OPT) ? toURI(getOption(REGISTRY_OPT)) : null;
         URI pipeLocation = toURI(args[0]);
         Map<Name,URI> inputMappings = new HashMap<Name,URI>();
         URI defaultInput = null;
         List<Name> inputOrder = new ArrayList<Name>();
         Name defaultPlaceHolder = Name.create("{x:x}x");
         for (int i=1; i<args.length; i++) {
            int sep = args[i].indexOf('=');
            if (sep<0) {
               if (defaultInput==null) {
                  defaultInput = toURI(args[i]);
                  inputOrder.add(defaultPlaceHolder);
                  continue;
               } else {
                  throw new XMLException("Default input already specified.  Additional inputs require a port name: "+args[i]);
               }
            }
            Name name = Name.create(args[i].substring(0,sep));
            URI inputDocument = toURI(args[i].substring(sep+1));
            inputMappings.put(name,inputDocument);
            inputOrder.add(name);
         }
         URI defaultOutput = null;
         Map<Name,URI> outputMappings = new HashMap<Name,URI>();
         Iterator<String> values = getOptionValues("out");
         if (values!=null) {
            while (values.hasNext()) {
               String spec = values.next();
               int sep = spec.indexOf('=');
               if (sep<0) {
                  if (defaultOutput!=null) {
                     throw new XMLException("There is more than one default output (need a port name): "+spec);
                  }
                  defaultOutput = toURI(spec);
                  continue;
               }
               Name name = Name.create(spec.substring(0,sep));
               URI outputDocument = toURI(spec.substring(sep+1));
               outputMappings.put(name,outputDocument);
            }
         }
         
         PipelineCompiler compiler = new FlowCompiler();
         if (registryLoc!=null) {
            ComponentRegistry reg = ComponentRegistryReader.getInstance().read(registryLoc);
            reg.getSubordinate().add(compiler.getComponentRegistry());
            compiler.setComponentRegistry(reg);
         }
         Document pipeDocument = compiler.getDocumentLoader().load(pipeLocation);
         Pipeline pipe = compiler.compile(pipeDocument);
         
         ComponentDefinition def = pipe.getDefinition();
         
         if (def.getInputs().size()!=inputMappings.size()
             && (defaultInput==null || (def.getInputs().size()-1)!=inputMappings.size())) {
            throw new XMLException("Number of inputs on pipeline do not match number of inputs specified.");
         }
         if (def.getOutputs().size()!=outputMappings.size()
             && (def.getOutputs().size()-1)!=outputMappings.size()) {
            throw new XMLException("Number of outputs on pipeline do not match number of outputs specified");
         }
         Name unboundOutput = null;
         for (Name name : def.getOutputs()) {
            if (outputMappings.get(name)==null) {
               if (unboundOutput==null) {
                  unboundOutput = name;
               } else {
                  throw new XMLException("More than one output is unbound (e.g. "+name+").");
               }
            }
         }
         Name unboundInput = null;
         for (Name name : def.getInputs()) {
            if (inputMappings.get(name)==null) {
               if (unboundInput==null && defaultInput!=null) {
                  unboundInput = name;
               } else {
                  throw new XMLException("Unbound input port "+name);
               }
            }
         }
         Component pipeComponent = def.newInstance();
         
         ComponentContextBase context = new ComponentContextBase(pipeLocation,pipeDocument.getDocumentElement().getNamespaceScope());
         
         pipeComponent.init(context);
         
         for (Name name : outputMappings.keySet()) {
            pipeComponent.bindOutputPort(name,getOutput(openURI(outputMappings.get(name))));
         }
         if (unboundOutput!=null) {
            if (defaultOutput!=null) {
               pipeComponent.bindOutputPort(unboundOutput,getOutput(openURI(defaultOutput)));
            } else {
               pipeComponent.bindOutputPort(unboundOutput,getOutput(new OutputStreamWriter(System.out,"UTF-8")));
            }
         }
         
         pipeComponent.onStart();
         for (Name name : inputOrder) {
            if (name==defaultPlaceHolder) {
               compiler.getDocumentLoader().generate(
                  defaultInput,
                  pipeComponent.getInputPort(unboundInput)
               );
            } else {
               compiler.getDocumentLoader().generate(
                  inputMappings.get(name),
                  pipeComponent.getInputPort(name)
               );
            }
         }
         pipeComponent.onEnd();
         
         return 0;
      } catch (XMLException ex) {
         PrintWriter err = new PrintWriter(System.err);
         ex.write(err);
         err.flush();
      } catch (IOException ex) {
         ex.printStackTrace();
      } catch (URISyntaxException ex) {
         ex.printStackTrace();
      }
      return 1;
   }
   
   public static void main(String [] args) {
      XProc cmd = new XProc();
      if (cmd.parseArguments(args)) {
         cmd.run();
         System.exit(cmd.getStatusCode());
      } else {
         System.exit(1);
      }
   }
   
}
